

//////////////////////////////// MODEL OBJECT ///////////////////////////////////////////
// MVC model object for the value of data points
/////////////////////////////////////////////////////////////////////////////////////////

// namespace for the MVC model, using the Module Pattern with anonymous constructor
// see http://yuiblog.com/blog/2007/06/12/module-pattern/ and http://www.dustindiaz.com/namespace-your-javascript/
// read the blog comments too
var LCMVC_model = new function() {
	
	////////////////////////////////// PRIVATE SOAP STUFF /////////////////////////////////////

	function WritePreset(dpName, presetValue) {
		var writeBody =	'<Write xmlns="http://wsdl.echelon.com/web_services_ns/ilon100/v4.0/message/">' +
			'<iLonItem>' +
				'<Item>' +
					'<UCPTname>' + dpName + '</UCPTname>' +
					'<UCPTvalue LonFormat="UCPTvalueDef">' + presetValue + '</UCPTvalue>' +
				'</Item>' +
			'</iLonItem>' +
		'</Write>';

		return writeBody;
	}

	function WriteRawValue(dpName, rawValue) {
		var writeBody =	'<Write xmlns="http://wsdl.echelon.com/web_services_ns/ilon100/v4.0/message/">' +
			'<iLonItem>' +
				'<Item>' +
					'<UCPTname>' + dpName + '</UCPTname>' +
					'<UCPTvalue>' + rowValue + '</UCPTvalue>' +
				'</Item>' +
			'</iLonItem>' +
		'</Write>';

		return writeBody;
	}
	

	// Assemble a Read() for all specified data points
	// dpList: Array (not associative) of strings, each representing a dp full path 
	function Read(dpList) {

		var itemList = "",
			readBody;

		/*
		// get arguments array from implicit variable "arguments"
		var argv = Read.arguments;
		var argc = argv.length;
		*/

		// add every dp in the list to the items to read
		for (var i = 0; i < dpList.length; i++) {
			itemList += '<Item xsi:type="Dp_Cfg">' +
				'<UCPTname>' + dpList[i] + '</UCPTname>' +
			'</Item>';
		}

		readBody = '<Read xmlns="http://wsdl.echelon.com/web_services_ns/ilon100/v4.0/message/">' +
			'<iLonItem>' + itemList + '</iLonItem>' +
		'</Read>';

		return readBody;
	}

	// pack up the full envelope for the soap message 
	function PrepareFullSoapMessage(body) {
		var soapEnvelope = '<?xml version="1.0" encoding="utf-8"?>' +
		'<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" ' +
		'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
		'encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> ' +
			'<Header>' +
				'<messageProperties xmlns="http://wsdl.echelon.com/web_services_ns/ilon100/v4.0/message/">' +
				'</messageProperties>' +
			'</Header>' +
			'<Body>' +
				body +
			'</Body>' +
		'</Envelope>';

		return soapEnvelope;
	}

	// Executes soap call and calls SuccessCallback() if successful
	// Returns the full soap message
	function MakeSoapCall(body, SuccessCallback) {

		// for debugging
		//console.log("MakeSoapCall()");
		//return;

		var fullSoapMessage = PrepareFullSoapMessage(body);

		//console.log("Ajax call to IP " + ipaddr);
		var xhr = $.ajax({
			//url: "http://" + ipaddr + "/WSDL/iLON100.wsdl",
			url: "/WSDL/iLON100.wsdl",
			username: "ilon",
			password: "ilon",
			type: "POST",
			timeout: 2000, // in milliseconds
			dataType: "xml",
			data: fullSoapMessage,
			contentType: "text/xml; charset=\"utf-8\"",
			success: SuccessCallback,
			error: function(XMLHttpRequest, textStatus, errorThrown) {
				if (textStatus) { console.log("Error status: " + textStatus); }
				if (errorThrown) { console.log("Error thrown: " + errorThrown.message); }
				console.log("Possible cross-domain scripting issues. Correct i.Lon's IP address?");
			},
			complete: function(textStatus, xmlHttpRequest) {
				console.log("  Complete status: " + textStatus);
			}
		});
		return fullSoapMessage;
	}



	function IlonItemNode2Json(data) {

		// convert to json the iLonItem node
		var rootJSON = $.xmlToJSON(data.getElementsByTagName("iLonItem")[0]),
			faults = rootJSON.UCPTfaultCount[0].Text;

		// Check errors UCPTfaultCount
		if ( faults !== '0') {
			console.log("UCPTfaultCount says there are " + faults + " errors");
			return; // do nothing
		}

		return rootJSON;
	}

	// Return the preset in UCPTvalue if any, otherwise return the string value 
	// UCPTvalue: nodeset, not node
	function GetCurrentValue_PresetIfAny(UCPTvalue) {
		// get the preset if there is one (i.e. lonformat is UCPTvalueDef)
		if (UCPTvalue.contains("@LonFormat", "UCPTvalueDef")) {
			var currentPreset = UCPTvalue.getNodesByAttribute("@LonFormat", "UCPTvalueDef")[0].Text;
			return currentPreset;
		} else {
			// if not get the simple text
			return UCPTvalue[0].Text;
		}
	}
	
	// Private inner object for the generic data point 
	// Must be initialised with the following params (in order):
	// newFullPath: string  
	// newJsonRepresentation: object representing the <Item> node (not nodeset!) according to the x2j.js plugin for jQuery
	// 	Note: if correct then 
	// 		jsonRepresentation === rootJSON.Item[n]
	// 	and
	//		jsonRepresentation.UCPTvalue[0].Text === rootJSON.Item[n].UCPTname[0].Text 
	function oDataPoint(newFullPath, newJsonRep) {
		// private properties
		// -- none
		// public properties
		this.fullPath = newFullPath || null;
		this.jsonRepresentation = newJsonRep || null;
		// public methods via prototype accesse in the constructor; it seems to work but is it valid?? what side effect does this thing have
		// if done here? a different prototpe per object created??
		oDataPoint.prototype.CurrentPresetOrValue = function() {
			if (this.jsonRepresentation) {
				return GetCurrentValue_PresetIfAny(this.jsonRepresentation.UCPTvalue);
			} else {
				return null;
			}
		};
		// private methods
		// -- none
	}
	// public methods via prototype
	// -- none
	
	// Private inner object for the main data point cache
	// NotifyController: callback function to notify the controller of updated data in the cache 
	function oDpCache(NotifyController) {
		
		// Private properties:	
		// the array that contains the dp objects
		// we'll use it as hash table where 
		// key = fullPath
		// value = dp object
		var dpCache = [];
		
		// public properties:
		// -- none
		
		// private methods:
		// Add or updates a dp in the cache
		function SaveDataPointInCache(dpFullPath, dpJsonRep ) {

				// if the dp is already in cache it gets overwritten
				dpCache[dpFullPath] = new oDataPoint(dpFullPath, dpJsonRep);
				console.log("SaveDataPointInCache() " + dpFullPath);
				// notify the controller here? (too? Only?)
		};
		
		// privileged methods: public but with access to private (and of course public) properties/methods
		//
		
		// Setup the list of dp to monitor
		oDpCache.prototype.ObserveDataPointPath = function(dpFullPath) {
				dpCache[dpFullPath] = new oDataPoint(dpFullPath); // is dpCache in the scope here?????????????????
		};
		oDpCache.prototype.DataPoint = function(dpFullPath) {
				return dpCache[dpFullPath];
		};
		oDpCache.prototype.UpdateObservedDataPoints = function(list) {

			/*
			// ilon date format: 2010-04-09T16:09:22.320Z      
			var d = new Date();
			var currentDate = sprintf("%d-%02d-%02dT" + "%02d:%02d:%02d.%dZ",
				d.getUTCFullYear() 	   , 
				d.getUTCMonth() +1	   , // months are 0-11
				d.getUTCDate()         , // days are 1-31
				d.getUTCHours() 	   ,
				d.getUTCMinutes() 	   ,
				d.getUTCSeconds() 	   ,
				d.getUTCMilliseconds() 
			);
        
			// read only the points changed after a given time
			var onlyReadChangedPoints_Body = '<?xml version="1.0" encoding="utf-8" ?><Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><Header><messageProperties xmlns="http://wsdl.echelon.com/web_services_ns/ilon100/v4.0/message/"></messageProperties></Header><Body><List xmlns="http://wsdl.echelon.com/web_services_ns/ilon100/v4.0/message/"><iLonItem><xSelect>//Item[UCPTlastUpdate&gt;"' + currentDate + '"]</xSelect></iLonItem></List></Body></Envelope>';
			*/

			MakeSoapCall(Read(list), function(data, textStatus, xmlHttpRequest) {
				console.log("Read() success status: " + textStatus);

				// convert to json the iLonItem node
				var rootJSON = IlonItemNode2Json(data);

				// for each dp read
				for (var i = 0; i < rootJSON.Item.length; i++) {

					// what dp are we updating?
					var dpName = rootJSON.Item[i].UCPTname[0].Text;
					var latestValue = GetCurrentValue_PresetIfAny(rootJSON.Item[i].UCPTvalue);

					// print to console name and value of dp
					console.log(dpName + " = " + latestValue);

					// save the new values for the point
					SaveDataPointInCache(dpName, rootJSON.Item[i]);
				}
				// tell the controller there are new values to update in the UI
				if( typeof (NotifyController) === "function") NotifyController();
			});
		};
	
		// write new values into the data point
		// in the future it will check with the cache before updating it
		oDpCache.prototype.Write = function(dpPath, newValue, SuccessCallback) {

			// if newValue is a preset
			// check that
			/*
				TODO 
			*/
			MakeSoapCall(WritePreset(dpPath, newValue), SuccessCallback);

			// if not write the raw value
			/*
				TODO 
			*/
		}
	
	}
	// Public methods via prototype
	// Use a literal object applied to the whole prototype ----- what happens to the original prototype object? was it empty?
	//oDpCache.prototype = {	}; 

	// make public whatever properties/methods we want, and make them accessible via the module name, i.e. LCMVC_model.xxxxxx
	// here we keep the DataPoint object private
	return {
		oDpCache: oDpCache, // access it as "var c = new LCMVC_model.oDpCache(...);"
	};
};

